#define DEBUG_TAG "appbase"

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <libgen.h>
#include <string.h>
#include <stdarg.h>
#include <sstream>
#include <fstream>
#include <algorithm>

extern "C" {
#include "pagemap/pagemap.h"
}

#include <openssl/sha.h>

#include "utils/utils.h"
#include "utils/Logger.h"

namespace utils {

using namespace std;

// ----------------- DirReader -----------------------------

DirReader::DirEntry::DirEntry(const char *_name, ino_t _ino, unsigned char _type)
	: name(_name), ino(_ino), type(_type)
{
}

bool DirReader::DirEntry::isFile()
{
	return (type == DT_REG);
}

bool DirReader::DirEntry::isDir()
{
	return (type == DT_DIR);
}

string &DirReader::getName()
{
	return entry->name;
}

string DirReader::getPath()
{
	string path = base;
	if (path.back() != '/')
		path += '/';
	path += entry->name;
	return path;
}

unsigned char DirReader::getType()
{
	return entry->type;
}

int DirReader::readDir(const char *_base)
{
	struct dirent *de;
	DIR *dir;

	base = _base;

	dir = opendir(_base);
	if (!dir) {
		log_err("Failed to open directory: %s\n", _base);
		return -1;
	}

	DirEntry *d;

	while ((de = readdir(dir))) {
		if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
			continue;

		entries.push_back(DirEntry(de->d_name, de->d_ino, de->d_type));

		d = &entries.back();
		if (d->isDir())
			hasSubDirs = true;

		log_debug("entry: %s, type: 0x%hhx\n", CS(d->name), d->type);
	}

	closedir(dir);

	return 0;
}

size_t DirReader::size()
{
	return entries.size();
}

void DirReader::start()
{
	started = false;
}

bool DirReader::next()
{
	if (!started) {
		it = entries.begin();
		started = true;
	} else {
		it++;
	}
	if (it != entries.end()) {
		entry = &*it;
		return true;
	}
	return false;
}

// ---------------  URI ----------------------------

void URI::operator=(const string &uri)
{
	size_t pos = 0, found, len;

	if (uri.empty())
		return;

	found = uri.find("://", pos);
	if (found != string::npos) {
		// we might have protocol
		len = found - pos;
		scheme = uri.substr(pos, len);
		pos += len + 3;
	} else {
		scheme = "";
	}

	if (!scheme.empty() && scheme != "file" && uri[pos] != '/') {
		// we have a domain
		found = uri.find('/', pos);
		len = ((found != string::npos) ? found : uri.length()) - pos;
		domain = uri.substr(pos, len);
		pos += len;
	} else {
		domain = "";
	}

	path = fix_path(uri.substr(pos));

	log_debug("scheme: %s, domain: %s, path: %s\n", CS(scheme), CS(domain), CS(path));
}

void URI::operator=(const URI &uri)
{
	scheme = uri.scheme;
	domain = uri.domain;
	path = uri.path;
}

URI::URI()
{
}

URI::URI(const URI &uri)
{
	*this = uri;
}

URI::URI(const string &uri)
{
	*this = uri;
}

bool URI::isRelative()
{
	if (scheme.empty() && domain.empty() && path[0] != '/')
		return true;
	return false;
}

void URI::prepandPath(const string &origin)
{
	path.insert(0, origin + "/");
}

string URI::get()
{
	if (scheme.empty())
		scheme = "file";
	string url = scheme;
	url += "://";
	url += domain;
	url += path;
	log_debug("url: %s\n", CS(url));
	return url;
}

bool URI::empty()
{
	return (path.empty() && domain.empty());
}

bool file_exists(const char *file, mode_t type, mode_t mode)
{
	struct stat st;

	if (lstat(file, &st))
		return false;

	if (!type)
		return true;

	if ((st.st_mode & S_IFMT) == type) {
		if (!mode)
			return true;
		if (st.st_mode & mode)
			return true;
	}

	return false;
}

bool file_exists(const string& file, mode_t type, mode_t mode)
{
	return file_exists(CS(file), type, mode);
}

string fix_path(const string& path)
{
	int length = path.length();
	string newpath;
	bool slash = false;
	int in;

	log_debug("len: %d\n", length);

	newpath.reserve(length + 1);

	for (in = 0; in < length; in++) {
		if (path[in] == '/') {
			if (slash)
				continue;
			slash = true;
		} else {
			slash = false;
		}
		newpath.push_back(path[in]);
	}

	return newpath;
}

int find(const dev_t dev, const char *path, int dts, find_cb_t func, void *arg)
{
	struct dirent *de;
	DIR *dir;
	int err = 0;

	if (dev) {
		struct stat st;
		int err = lstat(path, &st);

		if (err < 0) {
			log_err("Failed to stat: %s\n", path);
			return err;
		}
		if (st.st_dev != dev)
			return 0;
	}

	dir = opendir(path);
	if (!dir) {
		log_err("Failed to open directory %s\n", path);
		return -1;
	}

	if (fchdir(dirfd(dir))) {
		log_err("Failed to chdir %s\n", path);
		return -1;
	}

	while ((de = readdir(dir))) {
		if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, "."))
			continue;
		log_debug("path: %s, type: %u\n", de->d_name, de->d_type);
		if (de->d_type == DT_DIR) {
			err = find(dev, de->d_name, dts, func, arg);
			if (err)
				return err;
		} else if (dts & (1 << de->d_type)) {
			err = func(de->d_name, de->d_type, arg);
			if (err)
				return err;
		}
	}

	if (chdir("..")) {
		log_err("Failed to chdir: %s\n", path);
		return -1;
	}

	if (dts & DIR_MASK)
		err = func(path, DT_DIR, arg);

	closedir(dir);

	return err;
}

struct file_attrs {
	uid_t               uid;
	gid_t               gid;
	mode_t              fmode;
	mode_t              dmode;
	const char          *pkgid;
	const char          *seinfo;
};

static int set_attrs(const char *path, int type, void *arg)
{
	struct file_attrs *attrs = (struct file_attrs*)arg;
	mode_t mode = attrs->fmode;

	if (type == DT_DIR)
		mode = attrs->dmode;

	if (chown(path, attrs->uid, attrs->gid) < 0)
		return -errno;

	if (chmod(path, mode)< 0)
		return -errno;

	return 0;
}

int set_attrs(const string& path, uid_t uid, gid_t gid, mode_t fmode, mode_t dmode, const string& pkgid, const char *seinfo)
{
	struct file_attrs attrs = { .uid = uid, .gid = gid, .fmode = fmode, .dmode = dmode,
				    .pkgid = CS(pkgid), .seinfo = seinfo };

	int err = find(0, CS(path), REG_MASK | DIR_MASK, set_attrs, &attrs);
	if (err)
		log_err("Failed to set file attributes: %d\n", err);

	return err;
}

int sysexec(const string& exec)
{
	int err;
	int length = exec.length();
	char *argv[128];
	int i, argc, start, pos;
	char *cmdline = new char [length];

	log_debug("cmdline: %s\n", CS(exec));

	for (argc = 0, pos = 0, i = 0; i < length; i++) {
		// skip leading spaces
		if (isblank(exec[i]))
			continue;
		start = pos;
		char quote = 0;
		while (i < length) {
			if (exec[i] == '\"' || exec[i] == '\'') {
				if (!quote)
					quote = exec[i]; // open quote
				else if (exec[i] == quote)
					quote = 0; // close quote
				i++; // skip quote mark
			}
			if (isblank(exec[i]) && !quote)
				break;
			cmdline[pos++] = exec[i++];
		}
		cmdline[pos++] = '\0';
		argv[argc++] = cmdline + start;
		log_debug("token: %s\n", argv[argc - 1]);
	}
	argv[argc] = nullptr;

	err = execvp(argv[0], argv);
	if (err) {
		log_info("%s:%d:%s !!!!!!!!!!! err=%d \n",__FILE__, __LINE__, __FUNCTION__, err);
		log_err("Unable to execute '%s', exiting", argv[0]);
	}
	else {
		log_info("%s:%d:%s !!!!!!!!!!! \n",__FILE__, __LINE__, __FUNCTION__);
		log_info("execute '%s',  exiting", cmdline);
	}

	delete [] cmdline;
	return err;
}

// FIXME: eventually it must go away and replaced by native code
int vasystem(const char *format, ...)
{
	char cmd[4096];

	va_list ap;
	va_start(ap, format);
	vsnprintf(cmd, sizeof(cmd), format, ap);
	va_end(ap);

	log_debug("%s\n", cmd);
	// not needed at the moment because we have common lock
	//syscall.lock();
	int err = system(cmd);
	//syscall.unlock();
	log_debug("exit: %d\n", err);

	return WEXITSTATUS(err) ? -WEXITSTATUS(err) : err;
}

int copy_path(const string& from, const string& to, const string& file)
{
	int err;
	char *f = strdup(CS(file));
	char *dir = dirname(f);
	string cmd;

	string src = from;
	src += "/";
	src += file;

	string dest = to;
	dest += "/";
	if (strcmp(dir, "."))
		dest += dir;

	err = vasystem("mkdir -p \"%s\"", CS(dest));
	if (err) {
		log_err("mkdir -p: %d\n", err);
		goto exit;
	}

	err = vasystem("cp -p \"%s\" \"%s\"", CS(src), CS(dest));
	if (err)
		log_err("cp -p: %d\n", err);

exit:
	free(f);
	return err;
}

void rmrf(const string& path, bool recursive)
{
	if (recursive)
		vasystem("rm -rf %s", CS(path));
	else
		vasystem("rm -f %s", CS(path));
}

int mkdir(const char *pathname, uid_t uid, gid_t gid, mode_t mode)
{
	int err;

	err = ::mkdir(pathname, mode);
	if (err && errno != EEXIST)
		goto fail;

	err = chown(pathname, uid, gid);
	if (err)
		goto fail;

	err = chmod(pathname, mode);
	if (err)
		goto fail;

	return 0;
fail:
	log_err("Unable to create folder: %s", pathname);
	return err;
}

string getProcessName(pid_t pid)
{
	char proc[32];
	char cmdline[1024];
	FILE *fp;

	snprintf(proc, sizeof(proc), "/proc/%d/cmdline", pid);
	fp = fopen(proc, "r");
	if (!fp)
		return "";
	cmdline[0] = '\0';
	int unused __attribute__((unused));
	unused = fscanf(fp, "%s", cmdline);
	fclose(fp);
	return cmdline;
}

string getProcessLabel(pid_t pid)
{
	char proc[32];
	char label[1024];
	FILE *fp;

	snprintf(proc, sizeof(proc), "/proc/%d/attr/current", pid);
	fp = fopen(proc, "r");
	if (!fp)
		return "";
	label[0] = '\0';
	int unused __attribute__((unused));
	unused = fscanf(fp, "%s", label);
	fclose(fp);
	return label;
}

string getProcessExec(pid_t pid)
{
	char proc[32];
	char tmp[1024];

	snprintf(proc, sizeof(proc), "/proc/%d/exe", pid);
	ssize_t err = readlink(proc, tmp, sizeof(tmp));
	if (err < 0)
		return "";
	tmp[err] = '\0';
	return tmp;
}

uid_t getProcessUid(pid_t pid)
{
	char proc[32];
	char line[1024];
	FILE *fp;
	uid_t uid = 0;

	snprintf(proc, sizeof(proc), "/proc/%d/status", pid);
	fp = fopen(proc, "r");
	if (!fp)
		return 0;
	while(fgets(line, sizeof(line), fp) ) {
		if (sscanf(line, "Uid:	%d", &uid) == 1)
			break;
	}
	fclose(fp);
	return uid;
}

gid_t getProcessGid(pid_t pid)
{
	char proc[32];
	char line[1024];
	FILE *fp;
	gid_t gid = 0;

	snprintf(proc, sizeof(proc), "/proc/%d/status", pid);
	fp = fopen(proc, "r");
	if (!fp)
		return 0;
	while(fgets(line, sizeof(line), fp) ) {
		if (sscanf(line, "Gid:	%d", &gid) == 1)
			break;
	}
	fclose(fp);
	return gid;
}

int getProcessSchedulingState(pid_t pid)
{
	FILE *f;
	char fname[32];
	char tmp[1024];
	char *s;
	int pd;
	int state = -1;

	snprintf(fname, sizeof(fname) - 1, "/proc/%u/stat", pid);

	if ((f = fopen(fname, "r")) == NULL) { // Process probably doesn't exist
		return PROC_STATE_TERMINATED;
	}

	if (fgets(tmp, sizeof(tmp) - 1, f) == NULL) { // This should not happen
		fclose(f);
		return PROC_STATE_TERMINATED;
	}
	fclose(f);

	// Example: "15599 (defu) Z 15598 15598 1796 ..."
	if (sscanf(tmp, "%d", &pd) != 1) {
		return PROC_STATE_TERMINATED;
	}

	if (pd != pid) { // Sanity check (probably proces has terminated)
		return PROC_STATE_TERMINATED;
	}

	// Search for the character after ") ". 

	s = tmp;
	while (*s != 0 && *s != ')') {
		s++;
	}

	if (*s != ')') { // Not found (should not happen)
		return PROC_STATE_TERMINATED;
	}
	else {
		s += 2; // Skip the ") "

		switch (s[0]) { // Check the first character
		case 'R': state = PROC_STATE_RUNNING; break;		// S - Running (busy)
		case 'S': state = PROC_STATE_SLEEP; break;		// S - Sleeping in interruptible wait
		case 'D': state = PROC_STATE_DISK_SLEEP; break;		// D - Waiting in uninterruptible disk sleep
		case 'T': state = PROC_STATE_STOPPED; break;		// T - Process is stopped (on a signal) or being straced
		case 't': state = PROC_STATE_TRACED; break;		// t - Tracing stop (Linux 2.6.33 onward)
		case 'Z': state = PROC_STATE_ZOMBIE; break;		// Z - Zombie state
		case 'X': state = PROC_STATE_DEAD; break;		// X - Process is dead (from Linux 2.6.0 onward)
		case 'x': state = PROC_STATE_DEAD; break;		// x - Process is dead (Linux 2.6.33 to 3.13 only)
		case 'K': state = PROC_STATE_WAKEKILL; break;		// K - Wakekill
		case 'W': state = PROC_STATE_WAKING; break;		// W - Process is waking (was paging before 2.6.0)
		case 'P': state = PROC_STATE_PARKED; break;		// P - Parked (Linux 3.9 to 3.13 only)
		case '?': state = PROC_STATE_TERMINATED; break;		// Process doesn't exist (any more)

		default:  state = PROC_STATE_UNKNOWN;			// Unknown status (read error from /proc/<PID>/stat
			// or new status code)
		}
	}

	return state;
}

long getProcessSize(pid_t pid, long *pss, long *uss)
{
	static pm_kernel_t *kernel_handle = NULL;
	int error = 0;

	// Get memory usage (Uss)
	if (kernel_handle == NULL) {
		error = pm_kernel_create(&kernel_handle);
		if (error) {
			log_err("Error creating kernel interface -- "
				"does this kernel have pagemap?\n");
			return -1;
		}
	}

	if (kernel_handle) {
		pm_process_t *proc;
		error = pm_process_create(kernel_handle, pid, &proc);
		if (error) {
			log_err("warning: could not create process interface for %d\n", pid);
			return -1;
		}
		pm_memusage_t usage;

		error = pm_process_usage_flags(proc, &usage, 0, 0);
		pm_process_destroy(proc);

		if (!error) {
			*pss = usage.pss;
			*uss = usage.uss;
		}
	}

	return error;
}

void setOomAdj(pid_t pid, int adj)
{
	ostringstream proc;
	proc << "/proc/" << pid << "/oom_adj";
	ofstream file(proc.str());
	file << adj;
}

void getMemInfo(long& total, long &free, long& buffers, long& cached, long& shmem, long& slab)
{
	char buffer[1024];
	int numFound = 0;

	int fd = open("/proc/meminfo", O_RDONLY);

	if (fd < 0) {
		log_info("Unable to open /proc/meminfo: %s\n", strerror(errno));
		return;
	}

	const int len = read(fd, buffer, sizeof(buffer)-1);
	close(fd);

	if (len < 0) {
		log_info("Empty /proc/meminfo");
		return;
	}
	buffer[len] = 0;

	static const char* const tags[] = {
		"MemTotal:",
		"MemFree:",
		"Buffers:",
		"Cached:",
		"Shmem:",
		"Slab:",
		NULL
	};
	static const int tagsLen[] = {
		9,
		8,
		8,
		7,
		6,
		5,
		0
	};
	long mem[] = { 0, 0, 0, 0, 0, 0 };

	char* p = buffer;
	while (*p && numFound < 6) {
		int i = 0;
		while (tags[i]) {
			if (strncmp(p, tags[i], tagsLen[i]) == 0) {
				p += tagsLen[i];
				while (*p == ' ') p++;
				char* num = p;
				while (*p >= '0' && *p <= '9') p++;
				if (*p != 0) {
					*p = 0;
					p++;
				}
				mem[i] = atoll(num);
				numFound++;
				break;
			}
			i++;
		}
		while (*p && *p != '\n') {
			p++;
		}
		if (*p) p++;
	}

	total = mem[0] >> 10;
	free = mem[1] >> 10;
	buffers = mem[2] >> 10;
	cached = mem[3] >> 10;
	shmem = mem[4] >> 10;
	slab = mem[5] >> 10;
}

void strToLower(const string& cstr)
{
	string& str = const_cast<string&>(cstr);
	transform(str.begin(), str.end(), str.begin(), ::tolower);
}

void do_log_dump(const void *ptr, int len)
{
	int i, pos = 0;
	char buffer[1024];
	uint8_t *data = (uint8_t *) ptr;

	for (i = 0; i < len; i++)
		pos += snprintf(buffer + pos, sizeof(buffer) - pos, "%02x ", data[i]);
	log_info("%s", buffer);
}

// char *sha1encode(const char *input)
// {
// 	int n;
// 	static char out[(2 * SHA_DIGEST_LENGTH) + 1];
// 	unsigned char hash[SHA_DIGEST_LENGTH];
// 	static const char hex[] = "0123456789abcdef";

// 	memset(out, 0, sizeof(out));
// 	SHA1((unsigned char*)input, strlen(input), hash);

// 	for (n = 0; n < SHA_DIGEST_LENGTH; n++) {
// 		out[2 * n] = hex[hash[n] >> 4];
// 		out[2 * n + 1] = hex[hash[n] & 0x0f];
// 	}
// 	out[2 * n] = '\0';

// 	return out;
// }

}; // namespace appman
